// -------------------------------------------------
// BlinKit - BlinKit Library
// -------------------------------------------------
//   File Name: qjs_event_target.cpp
// Description: Bindings for EventTarget
//      Author: Ziming Li
//     Created: 2022-03-07
// -------------------------------------------------
// Copyright (C) 2022 MingYang Software Technology.
// -------------------------------------------------

#include "./qjs_bindings.h"

#include "blinkit/blink/renderer/bindings/core/qjs/qjs.h"
#include "blinkit/blink/renderer/bindings/core/qjs/qjs_c_function_list_entries.h"
#include "blinkit/blink/renderer/bindings/core/qjs/qjs_event_listener_impl.h"
#include "blinkit/blink/renderer/core/events/Event.h"
#include "blinkit/blink/renderer/core/events/event_target.h"

/**
 * https://developer.mozilla.org/en-US/docs/Web/API/EventTarget
 */

namespace BlinKit {
namespace qjs {

static void InitializeListenerOptions(EventListenerOptions &dst, JSContext *ctx, JSValue v)
{
    JSValue prop;

    prop = JS_GetPropertyStr(ctx, v, "capture");
    if (JS_IsBool(prop))
        dst.setCapture(JS_ToBool(ctx, prop));
    JS_FreeValue(ctx, prop);

    prop = JS_GetPropertyStr(ctx, v, "passive");
    if (JS_IsBool(prop))
        dst.setPassive(JS_ToBool(ctx, prop));
    JS_FreeValue(ctx, prop);
}

JSValue AddEventListenerImpl(JSContext *ctx, EventTarget *eventTarget, int argc, JSValueConst *argv)
{
    const AtomicString type = ToAtomicString(ctx, argv[0]);
    GCRefPtr<EventListener> listener = EventListenerImpl::Wrap(ctx, argv[1], true);

    bool useCapture = false;
    if (argc > 2)
    {
        if (JS_IsObject(argv[2]))
        {
            EventListenerOptions options;
            InitializeListenerOptions(options, ctx, argv[2]);
            eventTarget->addEventListener(type, listener.get(), options);
            return JS_UNDEFINED;
        }

        if (JS_IsBool(argv[2]))
            useCapture = JS_ToBool(ctx, argv[2]);
    }

    eventTarget->addEventListener(type, listener.get(), useCapture);
    return JS_UNDEFINED;
}

JSValue DispatchEventImpl(JSContext *ctx, EventTarget *eventTarget, int argc, JSValueConst *argv)
{
    Event *event = ScriptWrappable::Cast<Event>(argv[0]);

    Context c(*ctx);
    bool ret = eventTarget->dispatchEventForBindings(event, c);
    if (c.hadException())
        return c.ReturnException();
    return JS_NewBool(ctx, ret);
}

JSValue RemoveEventListenerImpl(JSContext *ctx, EventTarget *eventTarget, int argc, JSValueConst *argv)
{
    GCRefPtr<EventListener> listener = EventListenerImpl::Wrap(ctx, argv[1], false);
    if (!listener)
        return JS_UNDEFINED;

    const AtomicString type = ToAtomicString(ctx, argv[0]);

    bool useCapture = false;
    if (argc > 2)
    {
        if (JS_IsObject(argv[2]))
        {
            EventListenerOptions options;
            InitializeListenerOptions(options, ctx, argv[2]);
            eventTarget->removeEventListener(type, listener.get(), options);
            return JS_UNDEFINED;
        }

        if (JS_IsBool(argv[2]))
            useCapture = JS_ToBool(ctx, argv[2]);
    }

    eventTarget->removeEventListener(type, listener.get(), useCapture);
    return JS_UNDEFINED;
}

static JSValue AddEventListener(JSContext *ctx, JSValueConst thisVal, int argc, JSValueConst *argv)
{
    EventTarget *eventTarget = ScriptWrappable::Cast<EventTarget>(thisVal);
    return AddEventListenerImpl(ctx, eventTarget, argc, argv);
}

static JSValue DispatchEvent(JSContext *ctx, JSValueConst thisVal, int argc, JSValueConst *argv)
{
    EventTarget *eventTarget = ScriptWrappable::Cast<EventTarget>(thisVal);
    return DispatchEventImpl(ctx, eventTarget, argc, argv);
}

static JSValue RemoveEventListener(JSContext *ctx, JSValueConst thisVal, int argc, JSValueConst *argv)
{
    EventTarget *eventTarget = ScriptWrappable::Cast<EventTarget>(thisVal);
    return RemoveEventListenerImpl(ctx, eventTarget, argc, argv);
}

JSValue CreateEventTargetPrototype(JSContext *ctx)
{
    static const JSCFunctionListEntry Funcs[] = {
        CFunctionEntry(Strings::AddEventListener, 2, AddEventListener),
        CFunctionEntry(Strings::DispatchEvent, 1, DispatchEvent),
        CFunctionEntry(Strings::RemoveEventListener, 2, RemoveEventListener)
    };

    JSValue ret = JS_NewObject(ctx);
    JS_SetPropertyFunctionList(ctx, ret, Funcs, std::size(Funcs));
    return ret;
}

} // namespace qjs
} // namespace BlinKit
